# !/usr/bin/env python
# -*- coding:utf-8 -*-
import win32serviceutil
import win32service
import win32event
import logging

from threading import Timer
import tushare as ts
import re
import aiohttp
import asyncio
from datetime import datetime, timezone, timedelta
import time
import codecs
import inspect
import os
import urllib.request
import random


ua = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Safari/604.1.38"
]


class StockTicker:
    """
    实时行情报价器
    """

    _url = 'http://hq.sinajs.cn/list='

    def __init__(self, dir_path):
        self.code_list = self.get_code_list(self)  # 股票代码列表，至少每天更新一次
        self.logger = Logger(BatchFlushReciever(dir_path))  # 持久化记录
        self.timer = None

    @staticmethod
    def splist(l, s):
        """将list l按照s的长度分段"""
        return [l[i:i + s] for i in range(len(l)) if i % s == 0]

    @staticmethod
    def get_code_list(self):
        """将列表分段"""
        df = ts.get_stock_basics()
        full_list = list(map(lambda code: str('sh' + code[0:] if code[0] == '6' else 'sz' + code[0:]), list(df.index)))
        return list(map(lambda a: ','.join(a), self.splist(full_list, 400)))

    def update_stock_list(self):
        self.code_list = self.get_code_list(self)

    @staticmethod
    def get_header():
        return {
            "accept-charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.3",
            "accept-language": "en-US,en;q=0.8",
            "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
            "user-agent": random.choice(ua),
            "Accept-Encoding": "gzip,deflate",
        }

    @staticmethod
    async def fetch(session, url):
        """协程并发http请求"""
        with aiohttp.Timeout(10, loop=session.loop):
            async with session.get(url) as response:
                return await response.text()

    async def fetch_ticks(self, loop, url):
        """抓取tick数据"""
        async with aiohttp.ClientSession(loop=loop, headers=self.get_header()) as session:
            try:
                html = await self.fetch(session, url)
                # 将tick字段按逗号隔开
                rows = re.compile(r'(var hq_str_)|(\")|;', re.M).subn('', html)[0].replace('=', ',')
                # print(rows)
                for tick in rows.split('\n'):
                    if len(tick) > 10:
                        self.logger.tick_one(tick)
                # print(ticks)
                if self.timer and self.timer.isAlive:
                    self.timer.cancel()
                self.timer = Timer(20, self.flush)
                self.timer.start()
            except Exception as err:
                print(err)

    def run_ticker(self):
        """全量采集一次所有股票当前tick"""
        for stocks in self.code_list:
            loop = asyncio.get_event_loop()
            loop.run_until_complete(self.fetch_ticks(loop, self._url + stocks))

    def flush(self):
        self.logger.flush()
        if self.timer and self.timer.isAlive:
            self.timer.cancel()


class Logger:
    """
    数据记录类，接收全量混合的所有股票tick每只股票建立一个实体对象
    """
    def __init__(self, reciever):
        self.reciever = reciever
        self.stocks_dict = {}  # 去重hash map

    @staticmethod
    def get_code(tick):
        """获取股票代码"""
        m = re.search('s[hz][0-9]{6}', tick)
        if m:
            g = m.group()
            if isinstance(g, str):
                return g
        return None

    @staticmethod
    def get_event_time(tick):
        """获取时间"""
        m = re.search('[0-9]{4}-[0-9]{2}-[0-9]{2},[0-9]{2}:[0-9]{2}:[0-9]{2}', tick)
        if m:
            g = m.group()
            if isinstance(g, str):
                datetime.strptime(g, '%Y-%m-%d,%H:%M:%S')
                dtime = datetime.strptime(g, '%Y-%m-%d,%H:%M:%S').replace(tzinfo=timezone(timedelta(hours=8)))
                return int(time.mktime(dtime.timetuple()))
        return None

    def tick_one(self, tick):
        """记录一个tick"""
        code = self.get_code(tick)
        event_time = self.get_event_time(tick)
        tick_logger = self.stocks_dict.get(code)
        if not tick_logger:  # 未创建单条记录器
            tick_logger = TickLogger(code, self.reciever)
            self.stocks_dict[code] = tick_logger
        tick_logger.append(event_time, tick)

    def flush(self):
        for log in self.stocks_dict.values():
            log.flush()
        self.stocks_dict.clear()
        self.reciever.flush()


class BatchFlushReciever:
    def __init__(self, dir_path):
        self.dir_path = dir_path
        self.queue = []
        self.timer = None

    def append(self, item):
        self.queue.append(item)
        if len(self.queue) >= 8000:  # 缓存超出8000条记录则持久化
            self.flush()

    def flush(self):
        queue = sorted(self.queue[:])
        self.queue.clear()
        file = codecs.open(os.path.join(self.dir_path, datetime.today().strftime('%Y-%m-%d') + '.csv'), 'a', 'utf8')
        file.write('\n'.join(queue) + '\n')
        file.close()


class TickLogger:
    def __init__(self, code, reciever):
        self.code = code

        self.reciever = reciever
        self.tick_cache = {}  # 缓存堆栈

    def append(self, event_time, tick):
        """ 添加一条tick记录， 使缓冲区始终存在3条记录"""
        if not self.tick_cache.get(event_time):
            self.tick_cache[event_time] = tick
            if len(self.tick_cache) > 4:  # 缓存大于等于3，则清除最旧的一条缓存，进行持久化
                self.reciever.append(self.tick_cache.pop(sorted(self.tick_cache.keys())[0]))

    def flush(self):
        """清空缓冲区"""
        for et in sorted(self.tick_cache.keys()):
            self.reciever.append(self.tick_cache.get(et))
        self.tick_cache.clear()

class StockTickerService(win32serviceutil.ServiceFramework):
    r"""
    Usage: 'PythonService.py [options] install|update|remove|start [...]|stop|restart [...]|debug [...]'
    Options for 'install' and 'update' commands only:
     --username domain\username : The Username the service is to run under
     --password password : The password for the username
     --startup [manual|auto|disabled|delayed] : How the service starts, default = manual
     --interactive : Allow the service to interact with the desktop.
     --perfmonini file: .ini file to use for registering performance monitor data
     --perfmondll file: .dll file to use when querying the service for
       performance data, default = perfmondata.dll
    Options for 'start' and 'stop' commands only:
     --wait seconds: Wait for the service to actually start or stop.
                     If you specify --wait with the 'stop' option, the service
                     and all dependent services will be stopped, each waiting
                     the specified period.
    """
    # 服务名
    _svc_name_ = "StockTickerService"
    # 服务显示名称
    _svc_display_name_ = "股票tick数据采集"
    # 服务描述
    _svc_description_ = "Python 股票tick数据采集服务"

    def __init__(self, args):
        win32serviceutil.ServiceFramework.__init__(self, args)
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
        this_file = inspect.getfile(inspect.currentframe())
        self.dir_path = os.path.abspath(os.path.dirname(this_file))
        self.logger = self._getLogger(self.dir_path)
        self.isAlive = True
        self.ticker = StockTicker(self.dir_path)
        self.task = {
            "stock_list": '',
            "download": ''
        }

    def _getLogger(self, dir_path):
        logger = logging.getLogger('[StockTickerService]')

        handler = logging.FileHandler(os.path.join(dir_path, "service.log"))

        formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
        handler.setFormatter(formatter)

        logger.addHandler(handler)
        logger.setLevel(logging.INFO)

        return logger

    def SvcDoRun(self):
        self.logger.error("svc do run....")
        # while self.isAlive:
        #     self.logger.error("I am alive.")
        #     time.sleep(1)

        while self.isAlive:
            now = datetime.now()
            today_str = now.strftime('%Y%m%d')
            if today_str not in ['20180101', '20180215', '20180216', '20180219', '20180220', '20180221', '20180405', '20180406', '20180430', '20180501', '20180618', '20180924', '20181001', '20181002', '20181003', '20181003', '20181004', '20181005']:
                if now.weekday() < 5:
                    hms_int = int(now.strftime('%H%M%S'))
                    # 交易日9:00更新股票列表
                    if 90000 < hms_int and self.task.get("stock_list") != today_str:
                        self.task["stock_list"] = today_str
                        # 更新股票列表
                        self.ticker.update_stock_list()
                        self.logger.error("update stock list....")
                    if 91400 < hms_int < 113100 or 125900 < hms_int < 150600:
                        self.ticker.run_ticker()

                    if hms_int > 200000 and self.task.get("download") != today_str:
                        self.task["download"] = today_str
                        with urllib.request.urlopen("http://www.55188.com/viewthread.php?tid=5551406") as f:
                            st = str(f.read())
                            result = re.compile('https?://pan.baidu.com/s/[^"<>]+').search(st)
                            if result:
                                link = result.group()
                                date = datetime.today().strftime('%Y-%m-%d')
                                with open(os.path.join(self.dir_path, 'links.csv'), 'a') as file:
                                    file.writelines(date + ' ' + link + '\n')
                                    file.close()
            time.sleep(2)
            # 等待服务被停止
            # win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)

    # 停止服务
    def SvcStop(self):
        # 先告诉SCM停止这个过程
        self.logger.error("svc do stop....")
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        # 设置事件
        win32event.SetEvent(self.hWaitStop)
        self.isAlive = False


if __name__ == '__main__':
    win32serviceutil.HandleCommandLine(StockTickerService)
